
#include "CairoPaint.h"

#ifdef USE_CAIRO
namespace LuaGui {
	std::unordered_map<std::string, CairoImage*> CairoImage::s_images;

	CairoImage::CairoImage(const std::string& filepath)
	{
		m_filepath = filepath;
		m_surface = cairo_image_surface_create_from_png(filepath.c_str());

		cairo_status_t status = cairo_surface_status(m_surface);

		if (status != CAIRO_STATUS_SUCCESS)
		{
			if (status == CAIRO_STATUS_FILE_NOT_FOUND) {
				//GetApplication()->Info("Image File Not Exist: " + filepath);
			}
			else if (status == CAIRO_STATUS_NO_MEMORY) {
				//GetApplication()->Info("[Image] No Memory");
			}
			else if (status == CAIRO_STATUS_READ_ERROR) {
				//GetApplication()->Info("[Image] Can Not Read: " + filepath);
			}
			else if (status == CAIRO_STATUS_PNG_ERROR) {
				//GetApplication()->Info("[Image] Png File Format Error: " + filepath);
			}
		}
		m_expired_timestamp = GetTimeStamp();

		s_images[m_filepath] = this;
	}

	CairoImage::~CairoImage()
	{
		if (m_surface) cairo_surface_destroy(m_surface);
		s_images.erase(m_filepath);
	}

	void CairoImage::Refresh()
	{
		m_expired_timestamp = GetTimeStamp();
	}

	bool CairoImage::IsExpired()
	{
		unsigned long long cur_timestamp = GetTimeStamp();
		return (cur_timestamp - m_expired_timestamp) > MAX_ALIVE_TIME ? true : false;
	}

	cairo_surface_t* CairoImage::GetCairoSurface()
	{
		Refresh();
		return m_surface;
	}

	CairoImage* CairoImage::GetImageByFilePath(const std::string& filepath)
	{
		auto it = s_images.find(filepath);
		if (it != s_images.end()) return it->second;
		return new CairoImage(filepath);
	}

	void CairoImage::CollectGarbage()
	{
		static unsigned long long last_collect_garbage_ts = GetTimeStamp();
		unsigned long long cur_ts = GetTimeStamp();
		if ((cur_ts - last_collect_garbage_ts) < 3000) return;   // 3s 

		int size = (int)s_images.size();
		int max_size = MAX_CACHE_IMAGE_COUNT;
		for (std::unordered_map<std::string, CairoImage*>::iterator it = s_images.begin(); size > max_size && it != s_images.end();)
		{
			CairoImage* image = it->second;
			it++;

			if ((cur_ts - image->m_expired_timestamp) > MAX_ALIVE_TIME) {
				delete image;
				size--;
			}
		}
	}

	inline bool is_zero(double n) { return fabs(n) < 0.0001; }
	inline bool is_equal(double n1, double n2) { return is_zero(n1 - n2); }
	inline double get_range_value(double value, double min, double max) {
		if (value < min) return min;
		else if (value > max) return max;
		else return value;
	}

	CairoPaint::CairoPaint(cairo_t* cr)
	{
		m_cr = cr;
		m_font_face = NULL;
		m_ft_library = NULL;
		m_ft_face = NULL;
		FT_Init_FreeType(&m_ft_library);
	}

	CairoPaint::~CairoPaint()
	{
		if (m_ft_library) FT_Done_FreeType(m_ft_library);
	}

	bool CairoPaint::SetFont(std::string font_file)
	{
		if (m_ft_library == NULL) return false;
		if (0 != FT_New_Face(m_ft_library, font_file.c_str(), 0, &m_ft_face)) return false;
		m_font_face = cairo_ft_font_face_create_for_ft_face(m_ft_face, 0);
		if (m_cr) cairo_set_font_face(m_cr, m_font_face);
		return true;
	}

	bool CairoPaint::SetFont(unsigned char* data, uint32_t size)
	{
		if (m_ft_library == NULL) return false;
		if (0 != FT_New_Memory_Face(m_ft_library, data, size, 0, &m_ft_face)) return false;
		m_font_face = cairo_ft_font_face_create_for_ft_face(m_ft_face, 0);
		if (m_cr) cairo_set_font_face(m_cr, m_font_face);
		return true;
	}

	void CairoPaint::SetCairo(cairo_t* cr) {
		m_cr = cr;
		if (m_cr && m_font_face) cairo_set_font_face(m_cr, m_font_face);
	}

	void CairoPaint::DrawPoint(double x, double y)
	{
		cairo_rectangle(m_cr, x, y, 1, 1);
		Fill();
	}

	void CairoPaint::MoveTo(double x, double y)
	{
		cairo_move_to(m_cr, x, y);
	}

	void CairoPaint::LineTo(double x, double y)
	{
		cairo_line_to(m_cr, x, y);
	}

	void CairoPaint::DrawLine(double x1, double y1, double x2, double y2, bool stroke)
	{
		cairo_move_to(m_cr, x1, y1);
		cairo_line_to(m_cr, x2, y2);
		if (stroke) Stroke();
	}

	void CairoPaint::DrawTriangle(double x1, double y1, double x2, double y2, double x3, double y3, bool fill, bool stroke)
	{
		if (fill)
		{
			cairo_move_to(m_cr, x1, y1);
			cairo_line_to(m_cr, x2, y2);
			cairo_line_to(m_cr, x3, y3);
			cairo_line_to(m_cr, x1, y1);
			Fill();
		}

		if (stroke)
		{
			cairo_move_to(m_cr, x1, y1);
			cairo_line_to(m_cr, x2, y2);
			cairo_line_to(m_cr, x3, y3);
			cairo_line_to(m_cr, x1, y1);
			Stroke();
		}

		if (!fill && !stroke)
		{
			cairo_move_to(m_cr, x1, y1);
			cairo_line_to(m_cr, x2, y2);
			cairo_line_to(m_cr, x3, y3);
		}
	}

	void CairoPaint::DrawCurve(double x1, double y1, double x2, double y2, double x3, double y3, bool fill, bool stroke)
	{
		if (fill)
		{
			cairo_curve_to(m_cr, x1, y2, x2, y2, x3, y3);
			Fill();
		}

		if (stroke)
		{
			cairo_curve_to(m_cr, x1, y2, x2, y2, x3, y3);
			Stroke();
		}

		if (!fill && !stroke)
		{
			cairo_curve_to(m_cr, x1, y2, x2, y2, x3, y3);
		}
	}

	void CairoPaint::DrawRectangle(double x, double y, double w, double h, bool fill, bool stroke)
	{
		if (fill)
		{
			cairo_rectangle(m_cr, x, y, w, h);
			Fill();
		}

		if (stroke)
		{
			cairo_rectangle(m_cr, x, y, w, h);
			Stroke();
		}

		if (!fill && !stroke) cairo_rectangle(m_cr, x, y, w, h);
	}

	void CairoPaint::DrawArc(double cx, double cy, double radius, double angle1, double angle2, bool fill, bool stroke)
	{
		if (fill)
		{
			cairo_move_to(m_cr, cx, cy);
			cairo_arc(m_cr, cx, cy, radius, angle1, angle2);
			Fill();
		}
		if (stroke)
		{
			cairo_move_to(m_cr, cx + radius * cos(angle1), cy + radius * sin(angle1));
			cairo_arc(m_cr, cx, cy, radius, angle1, angle2);
			Stroke();
		}
		if (!fill && !stroke) {
			cairo_arc(m_cr, cx, cy, radius, angle1, angle2);
		}
	}

	void CairoPaint::DrawPolygon(std::vector<double>& points, bool fill, bool stroke)
	{
		int size = (int)points.size();
		if (size == 0 || (size % 2) != 0) return;
		int point_size = size / 2;

		for (int i = 0; i < 3; i++)
		{
			bool is_draw = (i == 0 && fill) || (i == 1 && stroke) || (i == 2 && !fill && !stroke);
			if (is_draw)
			{
				for (int j = 0; j < point_size; j++)
				{
					if (j == 0) cairo_move_to(m_cr, points[2 * j], points[2 * j + 1]);
					else cairo_line_to(m_cr, points[2 * j], points[2 * j + 1]);
				}
				cairo_line_to(m_cr, points[0], points[1]);
				if (i == 0)
				{
					Fill();
				}
				else if (i == 1)
				{
					Stroke();
				}
				else
				{
				}
			}
		}
	}

	void CairoPaint::DrawText(double x, double y, const char* utf8)
	{
		unsigned char r = 0;
		unsigned char g = 0;
		unsigned char b = 0;
		unsigned char a = 0;
		GetRGBAByColor(GetFontColor(), r, g, b, a);
		cairo_move_to(m_cr, x, y);
		cairo_set_source_rgba(m_cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
		cairo_set_font_size(m_cr, GetFontSize());
		cairo_show_text(m_cr, utf8);
	}

	void CairoPaint::DrawText(double x, double y, double w, double h, const wchar_t* unicode, const int x_align, const int y_align, bool is_clip) {
		Paint::DrawText(x, y, w, h, LuaGui::UnicodeToUtf8(unicode).c_str(), x_align, y_align, is_clip);
	}

	double CairoPaint::GetTextWidth(const char* utf8, double font_size)
	{
		// ֱ�Ӽ����ַ�������ʱ, ���ַ���β��Ϊ�հ��ַ�ʱ, �հ��ַ����ؿ��ƫС���²���
		//cairo_text_extents(m_cr, utf8, &extents);
		//if (pwidth) *pwidth = extents.width + 2 * extents.x_bearing;
		//if (pheight) *pheight = extents.height;
		// ����ַ���β��Ϊ�հ�
		cairo_set_font_size(m_cr, font_size);
		const char* ch = "A";  // �հ��ַ����㲻��
		std::string str = std::string(utf8) + std::string(ch);
		cairo_text_extents_t extents;
		cairo_text_extents(m_cr, ch, &extents);
		double ch_width = extents.width + 2 * extents.x_bearing;
		cairo_text_extents(m_cr, str.c_str(), &extents);
		double str_width = extents.width + 2 * extents.x_bearing;
		return str_width - ch_width;
	}

	double CairoPaint::GetTextWidth(const wchar_t* unicode, double font_size)
	{
		return GetTextWidth(LuaGui::UnicodeToUtf8(unicode).c_str(), font_size);
	}

	void CairoPaint::DrawImage(double dx, double dy, double dw, double dh, cairo_surface_t* image_surface, double sx, double sy, double sw, double sh, bool is_scale)
	{
		int iw = cairo_image_surface_get_width(image_surface);
		int ih = cairo_image_surface_get_height(image_surface);
		sw = sw < 0 ? iw : sw;
		sh = sh < 0 ? ih : sh;
		sx = get_range_value(sx, 0, iw);
		sy = get_range_value(sy, 0, ih);
		sw = get_range_value(sw, 0, iw);
		sh = get_range_value(sh, 0, ih);
		sx = (sx + sw) > iw ? (iw - sw) : sx;
		sy = (sy + sh) > ih ? (ih - sh) : sy;

		if (is_zero(sw) || is_zero(sh) || is_zero(dw) || is_zero(dh) || dw < 0 || dh < 0) return;
		double scale_x = dw / sw;
		double scale_y = dh / sh;

		cairo_save(m_cr);
		cairo_rectangle(m_cr, dx, dy, dw, dh);
		cairo_clip(m_cr);

		if (is_scale && (!is_equal(sw, dw) || !is_equal(sh, dh))) {
			cairo_scale(m_cr, scale_x, scale_y);
			dx /= scale_x;  // sx δΪ����ֵ ���轫dxҲ��Ϊδ����ֵ
			dy /= scale_y;  // sy δΪ����ֵ ���轫dyҲ��Ϊδ����ֵ
		}

		cairo_set_source_surface(m_cr, image_surface, dx - sx, dy - sy);
		cairo_paint(m_cr);
		cairo_restore(m_cr);
	}

	void CairoPaint::DrawNineImage(double x, double y, double w, double h, const std::string& utf8_png_filename, double l, double t, double r, double b)
	{
		cairo_surface_t* image_surface = CairoImage::GetImageByFilePath(utf8_png_filename)->GetCairoSurface();
		if (cairo_surface_status(image_surface) == CAIRO_STATUS_SUCCESS)
		{
			int iw = cairo_image_surface_get_width(image_surface);
			int ih = cairo_image_surface_get_height(image_surface);
			// ����
			DrawImage(x, y, l, t, image_surface, 0, 0, l, t, false);
			// ����
			DrawImage(x + l, y, w - l - r, t, image_surface, l, 0, iw - l - r, t, true);
			// ����
			DrawImage(x + w - r, y, r, t, image_surface, iw - r, 0, r, t, false);
			// ����
			DrawImage(x, y + t, l, h - t - b, image_surface, 0, t, l, ih - t - b, true);
			// ����
			DrawImage(x + l, y + t, w - l - r, h - t - b, image_surface, l, t, iw - l - r, ih - t - b, true);
			// ����
			DrawImage(x + w - r, y + t, r, h - t - b, image_surface, iw - r, t, r, ih - t - b, true);
			// ����
			DrawImage(x, y + h - b, l, b, image_surface, 0, ih - b, l, b, false);
			// ����
			DrawImage(x + l, y + h - b, w - l - r, b, image_surface, l, ih - b, iw - l - r, b, true);
			// ����
			DrawImage(x + w - r, y + h - b, r, b, image_surface, iw - r, ih - b, r, b, false);
		}
	}

	void CairoPaint::DrawNineImage(double x, double y, double w, double h, const std::wstring& unicode_png_filename, double l, double t, double r, double b)
	{
		DrawNineImage(x, y, w, h, LuaGui::UnicodeToUtf8(unicode_png_filename), l, t, r, b);
	}

	void CairoPaint::DrawImage(double dx, double dy, double dw, double dh, const std::string& utf8_png_filename, double sx, double sy, double sw, double sh, bool is_scale)
	{
		cairo_surface_t* image_surface = CairoImage::GetImageByFilePath(utf8_png_filename)->GetCairoSurface();
		if (cairo_surface_status(image_surface) == CAIRO_STATUS_SUCCESS)
		{
			DrawImage(dx, dy, dw, dh, image_surface, sx, sy, sw, sh, is_scale);
		}
	}

	void CairoPaint::DrawImage(double dx, double dy, double dw, double dh, const std::wstring& unicode_png_filename, double sx, double sy, double sw, double sh, bool is_scale)
	{
		DrawImage(dx, dy, dw, dh, LuaGui::UnicodeToUtf8(unicode_png_filename), sx, sy, sw, sh, is_scale);
	}

	void CairoPaint::Stroke()
	{
		unsigned char r = 0;
		unsigned char g = 0;
		unsigned char b = 0;
		unsigned char a = 0;
		GetRGBAByColor(GetPenColor(), r, g, b, a);
		cairo_set_source_rgba(m_cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
		cairo_set_line_width(m_cr, GetLineWidth());
		cairo_stroke(m_cr);
	}

	void CairoPaint::Fill()
	{
		unsigned char r = 0;
		unsigned char g = 0;
		unsigned char b = 0;
		unsigned char a = 0;
		GetRGBAByColor(GetBrushColor(), r, g, b, a);
		cairo_set_source_rgba(m_cr, r / 255.0, g / 255.0, b / 255.0, a / 255.0);
		cairo_fill(m_cr);
	}

	void CairoPaint::Flush()
	{
		cairo_surface_flush(cairo_get_target(m_cr));
	}

	void CairoPaint::Clip()
	{
		cairo_clip(m_cr);
	}

	void CairoPaint::Save()
	{
		cairo_save(m_cr);
	}

	void CairoPaint::Restore()
	{
		cairo_restore(m_cr);
	}

	void CairoPaint::Translate(double tx, double ty)
	{
		cairo_translate(m_cr, tx, ty);
	}

	void CairoPaint::Scale(double sx, double sy)
	{
		cairo_scale(m_cr, sx, sy);
	}

	void CairoPaint::Rotate(double angle)
	{
		cairo_rotate(m_cr, angle);
	}

	void CairoPaint::Clear()
	{
		if (m_cr == NULL) return;
		cairo_surface_t* surface = cairo_get_target(m_cr);
		unsigned char* data = cairo_image_surface_get_data(surface);
		int width = cairo_image_surface_get_width(surface);
		int height = cairo_image_surface_get_height(surface);
		memset(data, 0, width * height * GetColorByteCount());
		cairo_surface_flush(surface);
		cairo_surface_mark_dirty(surface);
	}

	unsigned char* CairoPaint::GetData()
	{
		cairo_surface_t* surface = cairo_get_target(m_cr);
		return cairo_image_surface_get_data(surface);
	}

	void CairoPaint::Dump()
	{
		cairo_surface_t* surface = cairo_get_target(m_cr);
		unsigned char* data = cairo_image_surface_get_data(surface);
		int width = cairo_image_surface_get_width(surface);
		int height = cairo_image_surface_get_height(surface);
		int color_bytes = GetColorByteCount();
		for (int i = 0; i < height; i++)
		{
			for (int j = 0; j < width; j++)
			{
				unsigned char r = data[i * width * color_bytes + j * color_bytes + 0];
				unsigned char g = data[i * width * color_bytes + j * color_bytes + 1];
				unsigned char b = data[i * width * color_bytes + j * color_bytes + 2];
				unsigned char a = data[i * width * color_bytes + j * color_bytes + 3];
				if (r != 0 || g != 0 || b != 0)
				{
					printf("x = %d, y = %d, r = %d, g = %d, b = %d\n", j, i, r, g, b);
				}
			}
		}
	}

	void CairoPaint::WriteToPng(const char* utf8_filename)
	{
		cairo_surface_write_to_png(cairo_get_target(m_cr), utf8_filename);
	}

}

#endif // USE_CAIRO

